﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Meow.Core.Log;
using Silk.NET.OpenGL;

namespace Meow.Core.Render
{
    internal class ShaderProgram: IDisposable
    {
        public readonly uint ID;
        public readonly GL GLcontext;

        bool disposed = false;

        public ShaderProgram(GL gl)
        {
            GLcontext = gl;

            ID = gl.CreateProgram();
        }

        public void GenVertexAndFragmentShaderAndLink(in string vert, in string frag)
        {
            Shader shaderVertex = new Shader(vert, GLcontext, ShaderType.VertexShader);
            Shader shaderFragment = new Shader(frag, GLcontext, ShaderType.FragmentShader);
            LinkVertexAndFragmentShader(shaderVertex, shaderFragment);
            shaderVertex.DeleteShader();
            shaderFragment.DeleteShader();
        }

        public void LinkVertexAndFragmentShader(Shader vertShader, Shader fragShader)
        {
            if (vertShader.Type == ShaderType.VertexShader && fragShader.Type == ShaderType.FragmentShader &&
                !vertShader.Deleted && !fragShader.Deleted)
            {
                GLcontext.AttachShader(ID, vertShader.ID);
                GLcontext.AttachShader(ID, fragShader.ID);

                GLcontext.LinkProgram(ID);

                GLcontext.GetProgram(ID, ProgramPropertyARB.LinkStatus, out int lStatus);
                if (lStatus != (int)GLEnum.True)
                    throw new Exception("Program failed to link: " + GLcontext.GetProgramInfoLog(ID));

                DeAttachShader(vertShader);
                DeAttachShader(fragShader);

            }
            else
            {
                Logger.LogMessage("Should check the Shaders", Logger.LogType.Error);

            }
        }

        public struct VertexAttribPointer
        {
            internal readonly uint _positionLoc;
            internal readonly VertexAttribPointerType _attribPointerType;
            internal readonly int _size;
            internal readonly uint _stride;
            internal readonly uint _offset;
            internal readonly bool _normalized;

            public VertexAttribPointer(uint positionLoc, VertexAttribPointerType attribPointerType, int size, uint stride, uint offset, bool normalized)
            {
                _positionLoc = positionLoc;
                _attribPointerType = attribPointerType;
                _size = size;
                _stride = stride;
                _offset = offset;
                _normalized = normalized;
            }
        }

        List<VertexAttribPointer> vertexAttribPointers = new List<VertexAttribPointer>(0);

        /// <summary>
        /// 构建Shader program中重要的一步，添加VertexAttribPointer。
        /// 这是用于指定Shader传入参数的结构，所以应该与Shader Program绑定
        /// </summary>
        /// <param name="positionLoc"></param>
        /// <param name="attribPointerType"></param>
        /// <param name="size"></param>
        /// <param name="stride"></param>
        /// <param name="offset"></param>
        /// <param name="normalized"></param>
        public void AddVertexAttribPointer(uint positionLoc, VertexAttribPointerType attribPointerType, int size, uint stride, uint offset, bool normalized)
        {
            vertexAttribPointers.Add(new VertexAttribPointer(positionLoc, attribPointerType, size, stride, offset, normalized));
        }

        /// <summary>
        /// 渲染batch时用于启用一个shader program
        /// </summary>
        public void Activate()
        {
            for (var i = 0; i < vertexAttribPointers.Count; i++)
            {
                EnableVertexAttribArray(vertexAttribPointers[i]);
                DebugUtil.CheckGLError(GLcontext);
            }


            GLcontext.UseProgram(ID);
            DebugUtil.CheckGLError(GLcontext);
        }

        unsafe void EnableVertexAttribArray(in VertexAttribPointer pointer)
        {
            GLcontext.EnableVertexAttribArray(pointer._positionLoc);
            GLcontext.VertexAttribPointer(pointer._positionLoc, pointer._size, pointer._attribPointerType, pointer._normalized, pointer._stride, (void*)pointer._offset);
        }

        public void SetUniform1(in string uniformName, float f)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform1(location, f);
        }

        public void SetUniform2(in string uniformName, in Vector2 vec2)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform2(location, vec2);
        }

        public void SetUniform3(in string uniformName, in Vector3 vec3)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform3(location, vec3);
        }

        public void SetUniform4(in string uniformName, in Vector4 vec4)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform4(location, vec4);
        }

        public void SetUniform1Int(in string uniformName, int i)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform1(location, i);
        }

        public void SetUniform2Int(in string uniformName, int v1, int v2)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform2(location, v1, v2);
        }

        public void SetUniform3Int(in string uniformName, int v1, int v2, int v3)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform3(location, v1, v2, v3);
        }

        public void SetUniform4Int(in string uniformName, int v1, int v2, int v3, int v4)
        {
            int location = GLcontext.GetUniformLocation(ID, uniformName);
            GLcontext.Uniform4(location, v1, v2, v3, v4);
        }

        bool DeAttachShader(Shader shader)
        {
            GLcontext.DetachShader(ID, shader.ID);

            return true;
        }

        public void Dispose()
        {
            if (disposed)
                return;

            GLcontext.DeleteProgram(ID);
            disposed = true;
        }

    }


    internal class Shader
    {
        public readonly uint ID;
        public readonly GL GLcontext;
        public readonly ShaderType Type;
        public bool Deleted { get; private set; }

        /// <summary>
        /// Create and compile the shader
        /// </summary>
        public Shader(string src, GL gl, ShaderType shaderType)
        {
            Deleted = false;
            GLcontext = gl;
            Type = shaderType;

            ID = gl.CreateShader(shaderType);
            gl.ShaderSource(ID, src);

            gl.CompileShader(ID);

            // check if shader was failed to compile
            gl.GetShader(ID, ShaderParameterName.CompileStatus, out int vStatus);
            if (vStatus != (int)GLEnum.True)
                throw new Exception("Vertex shader failed to compile: " + gl.GetShaderInfoLog(ID));
        }

        public void DeleteShader()
        {
            if (Deleted)
                return;

            GLcontext.DeleteShader(ID);
            Deleted = true;
        }

        ~Shader()
        {
            DeleteShader();
        }
    }
}
